package net.zoneland.zrdp.generator.util;

import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;

import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.VelocityContext;

import net.zoneland.zrdp.common.constant.GenConstants;
import net.zoneland.zrdp.common.utils.DateUtil;
import net.zoneland.zrdp.common.utils.JacksonUtils;
import net.zoneland.zrdp.common.utils.StringUtilPlus;
import net.zoneland.zrdp.generator.domain.GenTable;
import net.zoneland.zrdp.generator.domain.GenTableColumn;

/**
 * 模板处理工具类
 *
 * @author zonevue
 */
public class VelocityUtils {
    private VelocityUtils() {
        throw new IllegalStateException("Utility class");
    }
    /** 项目空间路径 */
    private static final String PROJECT_PATH = "main/java";

    /** mybatis空间路径 */
    private static final String MYBATIS_PATH = "main/resources/mapper";

    /** 默认上级菜单，系统工具 */
    private static final String DEFAULT_PARENT_MENU_ID = "3";

    /**
     * 设置模板变量信息
     *
     * @return 模板列表
     */
    public static VelocityContext prepareContext(final GenTable genTable) throws JsonProcessingException {
        final String moduleName = genTable.getModuleName();
        final String businessName = genTable.getBusinessName();
        final String packageName = genTable.getPackageName();
        final String tplCategory = genTable.getTplCategory();
        final String functionName = genTable.getFunctionName();

        final VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("tablePrefix", genTable.getTablePrefix());
        velocityContext.put("tplCategory", genTable.getTplCategory());
        velocityContext.put("tableName", genTable.getTableName());
        velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
        velocityContext.put("ClassName", genTable.getClassName());
        velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
        velocityContext.put("moduleName", genTable.getModuleName());
        velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
        velocityContext.put("businessName", genTable.getBusinessName());
        velocityContext.put("basePackage", getPackagePrefix(packageName));
        velocityContext.put("packageName", packageName);
        velocityContext.put("author", genTable.getFunctionAuthor());
        velocityContext.put("datetime", DateUtil.getDate());
        velocityContext.put("pkColumn", genTable.getPkColumn());
        velocityContext.put("importList", getImportList(genTable));
        velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
        velocityContext.put("columns", genTable.getColumns());
        velocityContext.put("table", genTable);
        velocityContext.put("dicts", getDicts(genTable));
        setMenuVelocityContext(velocityContext, genTable);
        if (GenConstants.TPL_TREE.equals(tplCategory)) {
            setTreeVelocityContext(velocityContext, genTable);
        }
        if (GenConstants.TPL_SUB.equals(tplCategory)) {
            setSubVelocityContext(velocityContext, genTable);
        }
        return velocityContext;
    }

    public static void setMenuVelocityContext(final VelocityContext context, final GenTable genTable) throws JsonProcessingException {
        final String options = genTable.getOptions();
        if (Objects.isNull(options)) {
            throw new NullPointerException("请先编辑->生成信息->上层菜单");
        }
        final JsonNode paramsNode = JacksonUtils.toJsonNode(options);
        final String parentMenuId = getParentMenuId(paramsNode);
        context.put("parentMenuId", parentMenuId);
    }

    public static void setTreeVelocityContext(final VelocityContext context, final GenTable genTable) throws JsonProcessingException {
        final String options = genTable.getOptions();
        final JsonNode paramsNode = JacksonUtils.toJsonNode(options);
        final String treeCode = getTreeCode(paramsNode);
        final String treeParentCode = getTreeParentCode(paramsNode);
        final String treeName = getTreeName(paramsNode);

        context.put("treeCode", treeCode);
        context.put("treeParentCode", treeParentCode);
        context.put("treeName", treeName);
        context.put("expandColumn", getExpandColumn(genTable));
        if (paramsNode.hasNonNull(GenConstants.TREE_PARENT_CODE)) {
            context.put("tree_parent_code", treeParentCode);
        }
        if (paramsNode.hasNonNull(GenConstants.TREE_NAME)) {
            context.put("tree_name", treeName);
        }
    }

    public static void setSubVelocityContext(final VelocityContext context, final GenTable genTable) {
        final GenTable subTable = genTable.getSubTable();
        final String subTableName = genTable.getSubTableName();
        final String subTableFkName = genTable.getSubTableFkName();
        final String subClassName = genTable.getSubTable().getClassName();
        final String subTableFkClassName = StringUtilPlus.convertToCamelCase(subTableFkName);

        context.put("subTable", subTable);
        context.put("subTableName", subTableName);
        context.put("subTableFkName", subTableFkName);
        context.put("subTableFkClassName", subTableFkClassName);
        context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName));
        context.put("subClassName", subClassName);
        context.put("subclassName", StringUtils.uncapitalize(subClassName));
        context.put("subImportList", getImportList(genTable.getSubTable()));
    }

    /**
     * 获取模板信息
     *
     * @return 模板列表
     */
    public static List<String> getTemplateList(final String tplCategory) {
        final List<String> templates = Stream.of(
            "vm/java/domain.java.vm",
            "vm/java/mapper.java.vm",
            "vm/java/service.java.vm",
            "vm/java/serviceImpl.java.vm",
            "vm/java/controller.java.vm",
            "vm/xml/mapper.xml.vm",
            "vm/sql/sql.vm",
            "vm/js/api.js.vm"
        ).collect(Collectors.toList());

        switch (tplCategory) {
            case GenConstants.TPL_CRUD:
                templates.add("vm/vue/index.vue.vm");
                break;
            case GenConstants.TPL_TREE:
                templates.add("vm/vue/index-tree.vue.vm");
                break;
            case GenConstants.TPL_SUB:
                templates.addAll(Arrays.asList("vm/vue/index.vue.vm", "vm/java/sub-domain.java.vm"));
                break;
            default:
        }
        return templates;
    }

    /**
     * 获取文件名
     */
    public static String getFileName(final String template, final GenTable genTable) {
        // 包路径
        final String packageName = genTable.getPackageName();
        // 模块名
        final String moduleName = genTable.getModuleName();
        // 大写类名
        final String className = genTable.getClassName();
        // 业务名称
        final String businessName = genTable.getBusinessName();

        final String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
        final String mybatisPath = MYBATIS_PATH + "/" + moduleName;
        final String vuePath = "vue";
        // 文件名称
        String fileName = "";
        if (template.contains("domain.java.vm")) {
            fileName = MessageFormat.format("{0}/domain/{1}.java", javaPath, className);
        } else if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory())) {
            fileName = MessageFormat.format("{0}/domain/{1}.java", javaPath, genTable.getSubTable().getClassName());
        } else if (template.contains("mapper.java.vm")) {
            fileName = MessageFormat.format("{0}/mapper/{1}Mapper.java", javaPath, className);
        } else if (template.contains("service.java.vm")) {
            fileName = MessageFormat.format("{0}/service/I{1}Service.java", javaPath, className);
        } else if (template.contains("serviceImpl.java.vm")) {
            fileName = MessageFormat.format("{0}/service/impl/{1}ServiceImpl.java", javaPath, className);
        } else if (template.contains("controller.java.vm")) {
            fileName = MessageFormat.format("{0}/controller/{1}Controller.java", javaPath, className);
        } else if (template.contains("mapper.xml.vm")) {
            fileName = MessageFormat.format("{0}/{1}Mapper.xml", mybatisPath, className);
        } else if (template.contains("sql.vm")) {
            fileName = businessName + "Menu.sql";
        } else if (template.contains("api.js.vm")) {
            fileName = MessageFormat.format("{0}/api/{1}/{2}.js", vuePath, moduleName, businessName);
        } else if (template.contains("index.vue.vm")) {
            fileName = MessageFormat.format("{0}/views/{1}/{2}/index.vue", vuePath, moduleName, businessName);
        } else if (template.contains("index-tree.vue.vm")) {
            fileName = MessageFormat.format("{0}/views/{1}/{2}/index.vue", vuePath, moduleName, businessName);
        }
        return fileName;
    }

    /**
     * 获取包前缀
     *
     * @param packageName 包名称
     * @return 包前缀名称
     */
    public static String getPackagePrefix(final String packageName) {
        final int lastIndex = packageName.lastIndexOf(".");
        return StringUtils.substring(packageName, 0, lastIndex);
    }

    /**
     * 根据列类型获取导入包
     *
     * @param genTable 业务表对象
     * @return 返回需要导入的包列表
     */
    public static Set<String> getImportList(final GenTable genTable) {
        final List<GenTableColumn> columns = genTable.getColumns();
        final GenTable subGenTable = genTable.getSubTable();
        final HashSet<String> importList = new HashSet<>();
        if (Objects.nonNull(subGenTable)) {
            importList.add("java.util.List");
        }
        final Map<String, List<String>> javaTypeToImports = new HashMap<>();
        javaTypeToImports.put(GenConstants.TYPE_DATETIME, Arrays.asList("java.time.LocalDateTime", "com.fasterxml.jackson.annotation.JsonFormat"));
        javaTypeToImports.put(GenConstants.TYPE_DATE, Arrays.asList("java.time.LocalDate", "com.fasterxml.jackson.annotation.JsonFormat"));
        javaTypeToImports.put(GenConstants.TYPE_TIME, Arrays.asList("java.time.LocalTime", "com.fasterxml.jackson.annotation.JsonFormat"));
        javaTypeToImports.put(GenConstants.TYPE_BIGDECIMAL, Collections.singletonList("java.math.BigDecimal"));

        columns.stream()
            .filter(column -> !column.isSuperColumn())
            .map(GenTableColumn::getJavaType)
            .map(javaTypeToImports::get)
            .filter(Objects::nonNull)
            .flatMap(Collection::stream)
            .distinct()
            .forEach(importList::add);

        return importList;
    }

    /**
     * 根据列类型获取字典组
     *
     * @param genTable 业务表对象
     * @return 返回字典组
     */
    public static String getDicts(final GenTable genTable) {
        final List<GenTableColumn> columns = genTable.getColumns();
        final Set<String> dicts = new HashSet<>();
        addDicts(dicts, columns);
        if (Objects.nonNull(genTable.getSubTable())) {
            final List<GenTableColumn> subColumns = genTable.getSubTable().getColumns();
            addDicts(dicts, subColumns);
        }
        return StringUtils.join(dicts, ", ");
    }

    /**
     * 添加字典列表
     *
     * @param dicts 字典列表
     * @param columns 列集合
     */
    public static void addDicts(final Set<String> dicts, final List<GenTableColumn> columns) {
        columns.stream()
            .filter(column -> !column.isSuperColumn())
            .filter(column -> isMatchingHtmlType(column.getHtmlType()))
            .map(GenTableColumn::getDictType)
            .filter(StringUtils::isNotEmpty)
            .map(column -> "'" + column + "'")
            .forEach(dicts::add);
    }

    private static boolean isMatchingHtmlType(final String htmlType) {
        return StringUtils.equalsAny(htmlType,
            GenConstants.HTML_SELECT,
            GenConstants.HTML_RADIO,
            GenConstants.HTML_CHECKBOX);
    }


    /**
     * 获取权限前缀
     *
     * @param moduleName 模块名称
     * @param businessName 业务名称
     * @return 返回权限前缀
     */
    public static String getPermissionPrefix(final String moduleName, final String businessName) {
        return MessageFormat.format("{0}:{1}", moduleName, businessName);
    }

    /**
     * 获取上级菜单ID字段
     *
     * @param paramsNode 生成其他选项
     * @return 上级菜单ID字段
     */
    public static String getParentMenuId(final JsonNode paramsNode) {
        if (paramsNode.hasNonNull(GenConstants.PARENT_MENU_ID)) {
            final String parentMenuId = paramsNode.get(GenConstants.PARENT_MENU_ID).asText();
            if (StringUtils.isNotEmpty(parentMenuId)) {
                return parentMenuId;
            }
        }
        return DEFAULT_PARENT_MENU_ID;
    }

    /**
     * 获取树编码
     *
     * @param paramsNode 生成其他选项
     * @return 树编码
     */
    public static String getTreeCode(final JsonNode paramsNode) {
        return getJsonNodeValue(paramsNode, GenConstants.TREE_CODE);
    }

    /**
     * 获取树父编码
     *
     * @param paramsNode 生成其他选项
     * @return 树父编码
     */
    public static String getTreeParentCode(final JsonNode paramsNode) {
        return getJsonNodeValue(paramsNode, GenConstants.TREE_PARENT_CODE);
    }

    /**
     * 获取树名称
     *
     * @param paramsNode 生成其他选项
     * @return 树名称
     */
    public static String getTreeName(final JsonNode paramsNode) {
        return getJsonNodeValue(paramsNode, GenConstants.TREE_NAME);
    }

    /**
     * 从jsonNode中 查找key 对应的value 的字符串值
     * @param paramsNode 传入的jsonnode
     * @param key 对应的key
     * @return key对应的value
     */
    private static String getJsonNodeValue(final JsonNode paramsNode, final String key) {
        if (paramsNode.hasNonNull(key)) {
            return StringUtilPlus.toCamelCase(paramsNode.get(key).asText());
        }
        return StringUtils.EMPTY;
    }
    /**
     * 获取需要在哪一列上面显示展开按钮
     *
     * @param genTable 业务表对象
     * @return 展开按钮列序号
     */
    public static int getExpandColumn(final GenTable genTable) throws JsonProcessingException {
        final String options = genTable.getOptions();
        final JsonNode paramsNode = JacksonUtils.toJsonNode(options);
        final String treeName = paramsNode.get(GenConstants.TREE_NAME).asText();

        int num = 0;
        for (final GenTableColumn column : genTable.getColumns()) {
            if (column.isList()) {
                num++;
                final String columnName = column.getColumnName();
                if (columnName.equals(treeName)) {
                    break;
                }
            }
        }
        return num;
    }
}
